
#include <wx/sstream.h>
#include <wx/file.h>
#include <wx/filename.h>

#include "MsnSession.h"
#include "MsnEvent.h"
#include "MsnCommand.h"
#include "MsnMessenger.h"
#include "MsnSession.h"
#include "P2PConnection.h"
#include "SessionConnection.h"

#include "FileTransferEntry.h"
#include "FileTransferManager.h"


P2PConnection::P2PConnection(MsnSession* _session, const wxString& sid)
		:wxThread(wxTHREAD_JOINABLE),
		session(_session), sessionID(sid), serverSocket(0), socket(0)
{
	userCancelled = false;
	byeReceived = false;
}

P2PConnection::~P2PConnection()
{
	DEBUG(_T("~P2PConnection"));

	wxDELETE(socket);
}

void P2PConnection::SendChunk(char* chunk, unsigned int size)
{
	socket->Write(chunk, size);

}

void P2PConnection::ReadChunk(char* buf, unsigned int size)
{
    unsigned int read = 0;
    while(read < size){
        socket->Read(buf+read, size-read);

		int n = socket->LastCount();
        read += n;

        if(socket->Error()){
           break;
        }

        //wxString log;
        //log<<_T("read ")<<read<<_T(" bytes");
    }
    //buf[len] = 0;
    return;
}

bool P2PConnection::TestSocketReadable()
{
	//even true is returned by WaitForRead, it's still possible that there is no data to read. 
	//Hense we have to do Peek to ensure the data availability
	if(socket->WaitForRead(0, 0)){
		socket->SetFlags(wxSOCKET_NOWAIT);

		char buf[1] = {0};
		socket->Peek(buf, 1);

		if(socket->LastCount() > 0){
			return true;
		}

		DEBUG(_T("no data available, change the flag back "));

		socket->SetFlags(wxSOCKET_WAITALL);
	}

	return false;
}


void P2PConnection::Start()
{
    if((this->Create() == wxTHREAD_NO_ERROR )){
          if(this->Run() == wxTHREAD_NO_ERROR){
              return;
          }
     }

	WARN(_("Starting thread failed"));
}

int P2PConnection::OpenServerSocket(int port)
{
//	DEBUG(_T("OpenServerSocket: ") + StringUtil::ToString(port));

	int tried = 0;

    wxIPV4address addr;
    addr.Service(port);

    serverSocket = new wxSocketServer(addr);
    while (!serverSocket->Ok()){

		//no need to check what kind of error it is, sometimes memory error occurs when invoking LastError()
		//serverSocket->LastError() == wxSOCKET_INVPORT
		
		if(tried < 10){
			serverSocket->Destroy(); 

			tried ++;
			port++;

			DEBUG(wxString::Format(_T("try next port: %d"), port));
			
			addr.Service(port);
			serverSocket = new wxSocketServer(addr);

		}else{
			//tried 10 times and failed
			DEBUG(wxT("tried OpenServerSocket 10 times, still failed"));
			serverSocket->Destroy();
			return -1;
		}     
    }

    return port;
}


/*
IPv4Internal-Addrs: 192.168.237.1 192.168.153.1 221.137.88.252
IPv4Internal-Port: 1247
*/
bool P2PConnection::ConnectSocket(const wxString& address, int port)
{
			
	DEBUG(_T("connecting to: ")+address+_T(":")+wxString::Format(_T("%d"), port));
  
	socket = new wxSocketClient();
	socket->SetFlags(wxSOCKET_WAITALL | wxSOCKET_BLOCK );

	//only for connecting in case it last too long
	socket->SetTimeout(1);

	wxString ips = address;

	while(true){
		wxString ip = ips.AfterLast(' ');

		wxIPV4address addr;
		addr.Hostname(ip);
		addr.Service(port);

		DEBUG(_T("connecting to ") + ip + _T(":") + wxString::Format(_T("%d"), port));
		bool connected = ((wxSocketClient*)socket)->Connect(addr, true);

		if(connected){
			DEBUG(_T("connected with ") + ip + _T(":") + wxString::Format(_T("%d"), port));
			workMode = CLIENT;
			socket->SetTimeout(30);
			return true;
		}

		ips = ips.BeforeLast(' ');

		if(ips.IsEmpty()){
			DEBUG(_T("can not connect with ")+address+_T(":")+wxString::Format(_T("%d"), port));
			return false;
		}
	}
}

bool P2PConnection::AcceptSocket()
{
	DEBUG(wxString::Format(_T("waiting for incoming connection")));

	if(serverSocket->WaitForAccept(5)){
		socket = serverSocket->Accept();
		socket->SetFlags(wxSOCKET_WAITALL | wxSOCKET_BLOCK );
		DEBUG(_T("connection accepted "));
		serverSocket->Destroy();

		workMode = SERVER;

		return true;
	}else{
		//no imconming connection
		DEBUG(_T("waiting timeout "));

		//session->OnFileTransferTimeout(cookie);

		serverSocket->Destroy();
		return false;
	}	
}


void P2PConnection::Cancel()
{
	DEBUG(_T("p2p connection is cancelled"));

	userCancelled = true;
	//Close();
}

void P2PConnection::Close()
{
	if(socket){
		socket->Close();
	}
}

void P2PConnection::SendHandShake()
{
	DEBUG(_T("SendHandShake"));

	//send foo
	char foo[8] = { 0x04, 00, 00, 00, 0x66, 0x6F, 0x6F, 00};
	this->SendChunk(foo, 8);

	//handshake
	char head[4] = {0};
	ReadChunk(head, 4);
	DEBUG(_T("p2p handshake len:\r\n") + StringUtil::ToHexFormat((unsigned char*)head, 4));

	int len = StringUtil::ReadInt(head, 0, 4);
	char* buf = new char[len];
	ReadChunk(buf, len);
	DEBUG(_T("p2p handshake body: \r\n") + StringUtil::ToHexFormat((unsigned char*)buf, len));

	//handshake reply
	SendChunk(head, 4);
	buf[4] = buf[4] + 1;
	SendChunk(buf, 48);

	delete[] buf;	
}

void P2PConnection::ReceiveHandShake()
{
	DEBUG(_T("ReceiveHandShake"));

	char foo[8] = {0};
	ReadChunk(foo, 8);
	DEBUG(_T("p2p handshake foo received: ") + StringUtil::ToHexFormat((unsigned char*)foo, 8));

	//handshake
	char head[4] = {0};
	StringUtil::WriteInt(head, 0, 48);

	char handShake[48] = {0};
	
	StringUtil::WriteInt(handShake, 4, 1024);

	StringUtil::WriteInt(handShake, 28, 0x100);

// {A175812B-337E-4C50-4280-AA2C176B5ACD}
	handShake[32] = 0x2B;
	handShake[33] = 0x81;
	handShake[34] = 0x75;
	handShake[35] = 0xA1;

	handShake[36] = 0x7E;
	handShake[37] = 0x33;

	handShake[38] = 0x50;
	handShake[39] = 0x4C;

	handShake[40] = 0x42;
	handShake[41] = 0x80;

	handShake[42] = 0xAA;
	handShake[43] = 0x2C;
	handShake[44] = 0x17;
	handShake[45] = 0x6B;
	handShake[46] = 0x5A;
	handShake[47] = 0xCD;

	SendChunk(head, 4);
	SendChunk(handShake, 48);

	DEBUG(_T("p2p handshake sent:\r\n") + StringUtil::ToHexFormat((unsigned char*)handShake, 48));

	ReadChunk(head, 4);
	DEBUG(_T("p2p handshake reply len:\r\n") + StringUtil::ToHexFormat((unsigned char*)head, 4));

	int len = StringUtil::ReadInt(head, 0, 4);
	char* buf = new char[len];
	ReadChunk(buf, len);
	DEBUG(_T("p2p handshake reply body: \r\n") + StringUtil::ToHexFormat((unsigned char*)buf, len));

	delete[] buf;
}

void P2PConnection::SendP2PACKMessage(P2PMessage* msg)
{
	DEBUG(_T("SendP2PACKMessage"));

	char head[4] = {0x30, 00, 00, 00};
	SendChunk(head, 4);

	//this p2p message doesn't have a footer
	P2PMessage* ack = session->MakeP2PACKMessage(msg);
	wxMemoryBuffer buf;
	ack->EncodeBinaryHeader(buf);
	SendChunk((char*)buf.GetData(), buf.GetDataLen());
	delete ack;
}

void P2PConnection::SendP2PByeMessage()
{
	DEBUG(_T("SendP2PByeMessage"));

	wxString to = session->GetContact(0);
	wxString from =  session->GetMessenger()->GetUserPassport();

	//todo send bye message
	SLPMessage* slpMsg = 
			session->MakeSLPMessage( _T("bye"), 
						_T("application/x-msnmsgr-sessionclosebody"), 
						to, 
						from,
						_T("MSNSLP/1.0/TLP ;branch={AAFE6284-6312-41E3-B96F-70E378F62884}"),
						0,
						callID);

	slpMsg->SetProperty(_T("SessionID"), sessionID);

	slpMsg->SetProperty(_T("SChannelState"), _T("0"));
	slpMsg->SetProperty(_T("Capabilities-Flags"), _T("1"));

	//!!!must set the session id to 0, otherwise msn can not recognize bye message!!!
	P2PMessage* bye = session->MakeP2PMessage(0, slpMsg);

	wxMemoryBuffer byeBuf;
	bye->EncodeBinaryHeader(byeBuf);
	slpMsg->Encode(byeBuf);

	char head[4] = {0};
	StringUtil::WriteInt(head, 0, byeBuf.GetDataLen());
	SendChunk(head, 4);
	SendChunk((char*)byeBuf.GetData(), byeBuf.GetDataLen());

	DEBUG(bye->ToString());

	delete bye;
}



P2PFileReceiver::P2PFileReceiver(MsnSession* _session, const wxString& sid)
	:P2PConnection(_session, sid)
{

}


void* P2PFileReceiver::Entry()
{
	DEBUG(_T("receiver thread start: ") + wxString::Format(_T("%d"),this->GetId()));

	if(socket == NULL || !socket->IsConnected()){
		DEBUG(_T("socket not connected, thread exit"));
		return 0;
	}

	if(workMode == CLIENT){
		SendHandShake();
	}else{
		ReceiveHandShake();
	}

	FileTransferEntry* item = session->GetFtManager()->GetEntry(sessionID);

	session->GetFtManager()->NotifyStartedEvent(sessionID);

	//file data chunk	
	while (!TestDestroy()){

		if(userCancelled){
			DEBUG(_T("p2p connection cancelled by user"));
			SendP2PByeMessage();

			session->GetFtManager()->NotifyFinishedEvent(sessionID);
			break;
		}

		if(byeReceived){
			DEBUG(_T("p2p connection received BYE message"));

			session->GetFtManager()->NotifyFinishedEvent(item->GetID());
			break;
		}

	
		char head[4] = {0};
		ReadChunk(head, 4);
		
		int len = StringUtil::ReadInt(head, 0, 4);

		//TODO check the len range

		char* chunk = new char[len];
		ReadChunk(chunk, len);

		//DEBUG(_T("p2p message len:\r\n") + StringUtil::ToHexFormat((unsigned char*)head, 4));
		//DEBUG(_T("p2p message body: \r\n") + StringUtil::ToHexFormat((unsigned char*)chunk, len));

		P2PMessage* msg = new P2PMessage;
		msg->DecodeBody(chunk, len);

		delete[] chunk;

		if(msg->IsDataPreparationMessage()){
			DEBUG(_T("p2p connection received data preparation message"));
			SendP2PACKMessage(msg);
			
		}else if(msg->IsDataMessage()){
			bool done = session->GetFtManager()->WriteFileData(msg);

			session->GetFtManager()->NotifyUpdatedEvent(sessionID, msg->GetHeader()->messageLength);

			if(done){
				DEBUG(_T("p2p connection data transfer done "));
				SendP2PACKMessage(msg);
				session->GetFtManager()->NotifyFinishedEvent(item->GetID());	
			}

		}else{
			msg->DecodeSlpMessage();

			SendP2PACKMessage(msg);

			if(msg->GetSlpMessage()->GetMethod().StartsWith(_T("BYE MSNMSGR:"))){
				DEBUG(_T("p2p connection received BYE message"));

				DEBUG(msg->ToString());
				
				//check done or cancelled
				byeReceived = true;

			}else{
				DEBUG(_T("p2p connection received unexpected message"));
				DEBUG(msg->ToString());

				//this message is received in SERVER mode, don't know its purpose.
				/*
					ACK MSNMSGR:tuxitty2@hotmail.com MSNSLP/1.0
					To: <msnmsgr:tuxitty2@hotmail.com>
					From: <msnmsgr:tuxitty9@hotmail.com>
					Via: MSNSLP/1.0/TLP ;branch={42C61CA1-A070-4A68-98DC-80FFD08AF7E4}
					CSeq: 0 
					Call-ID: {00000000-0000-0000-0000-000000000000}
					Max-Forwards: 0
					Content-Type: application/x-msnmsgr-transdestaddrupdate
					Content-Length: 236

					IPv4ExternalAddrsAndPorts: 210.13.80.206:1226
					IPv4InternalAddrsAndPorts: 192.168.237.1:1719 192.168.153.1:1719 192.168.72.72:1719
					IPv4External-Connecting-Port-End-Range: 1239
					SessionID: 0
					SChannelState: 0
					Capabilities-Flags: 1			
				*/
			}
		}	

		delete msg;

        if(socket->Error()){
			DEBUG(wxString::Format(_T("socket error occured: %d "), socket->LastError()));

            break;
        }
    }

	Close();

	DEBUG(_T("connection thread exit:")+ wxString::Format(_T("%d"),this->GetId()));
    return 0;
}


BEGIN_EVENT_TABLE(P2PFileSender, wxEvtHandler)
        EVT_SOCKET(SOCKET_ID, P2PFileSender::OnSocketEvent)
END_EVENT_TABLE()

P2PFileSender::P2PFileSender(MsnSession* _session, const wxString& sid)
	:P2PConnection(_session, sid)
{

}


/*
	The event doesn't work here. 
	Though the callback will be triggered by the event loop, the variable in the thread object
	is not accessable. Done't know why. Have to use WaitForRead() in the thread entry instead.
*/
void P2PFileSender::OnSocketEvent(wxSocketEvent& event)
{

  switch(event.GetSocketEvent())
  {
    case wxSOCKET_INPUT: 
		DEBUG(_T("wxSOCKET event in thread: INPUT")); 
		//HandleInputEvent();

		{
			//wxMutexLocker lock(testMutex); 
			//cancelled = true;
		}
		DEBUG(wxString::Format(_T("set status to true: %d"), &testClass.testString222)); 
		break;
    
	case wxSOCKET_LOST: 
		break;
  }

 
}

void P2PFileSender::HandleInputEvent()
{
	DEBUG(_T("HandleInputEvent")); 

	char head[4] = {0};

	ReadChunk(head, 4);
	DEBUG(_T("p2p sender received data len:\r\n") + StringUtil::ToHexFormat((unsigned char*)head, 4));

	int recLen = StringUtil::ReadInt(head, 0, 4);
	char* recChunk = new char[recLen];
	ReadChunk(recChunk, recLen);
	DEBUG(_T("p2p sender received data:\r\n") + StringUtil::ToHexFormat((unsigned char*)recChunk, recLen));

	P2PMessage* recMsg = new P2PMessage;
	recMsg->DecodeBody(recChunk, recLen);

	recMsg->DecodeSlpMessage();

	DEBUG(recMsg->ToString());

	delete[] recChunk;

	//cancelled by receiver
	if(recMsg->GetSlpMessage()->GetMethod().StartsWith(_T("BYE MSNMSGR:"))){
		SendP2PACKMessage(recMsg);

		byeReceived = true;

		//session->GetFtManager()->NotifyFinishedEvent(sessionID);
	}
			
	delete recMsg;	
}


void P2PFileSender::RegisterSocketEvent()
{
	DEBUG(wxString::Format(_T("register thread : %d"), this)); 

	socket->SetEventHandler(*this, SOCKET_ID);
	socket->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG );
    socket->Notify(true); 
}


void* P2PFileSender::Entry()
{
	DEBUG(_T("sender thread start: ") + wxString::Format(_T("%d"),this->GetId()));

	if(workMode == CLIENT){
		SendHandShake();
	}else{
		ReceiveHandShake();
	}

	//RegisterSocketEvent();

	FileTransferEntry* item = session->GetFtManager()->GetEntry(sessionID);


	//send data preparation message
	if(item->GetAppID() == MSN_P2P_APP_ID_DISPLAY_PICTURE){
		DEBUG(_T("send data preparation message"));
		char body[4] = {0};	
		P2PMessage* prepMsg = session->MakeP2PDataMessage(StringUtil::ToLong(sessionID), item->GetMessageID() + 1, 0, 4, 4, 0, body);
		prepMsg->SetFooter(1);

		wxMemoryBuffer buf;
		prepMsg->EncodeBinaryHeader(buf);
		buf.AppendData(body, 4);
		prepMsg->EncodeBinaryFooter(buf);

		char head[4] = {0};
		StringUtil::WriteInt(head, 0, buf.GetDataLen());
		SendChunk(head, 4);
		SendChunk((char*)buf.GetData(), buf.GetDataLen());

		DEBUG(prepMsg->ToString());


		ReadChunk(head, 4);
		DEBUG(_T("p2p ack len:\r\n") + StringUtil::ToHexFormat((unsigned char*)head, 4));

		int len = StringUtil::ReadInt(head, 0, 4);
		char* chunk = new char[len];
		ReadChunk(chunk, len);

		DEBUG(_T("p2p ack data:\r\n") + StringUtil::ToHexFormat((unsigned char*)chunk, len));

		delete[] chunk;
	}


	while (!TestDestroy()){
	
		if(userCancelled){
			DEBUG(_T("p2p connection cancelled by user"));
			SendP2PByeMessage();
			session->GetFtManager()->NotifyFinishedEvent(sessionID);
			break;
		}

		if(byeReceived){
			session->GetFtManager()->NotifyFinishedEvent(sessionID);
			break;
		}
		
		//check if there's any data from the receiver, either a cancel message or other messages
		if(TestSocketReadable()){
			DEBUG(_T("data is available"));

			HandleInputEvent();

			continue;
		}

		//check socket
		//socket->IsConnected()
		if(socket->Error()){
			DEBUG(wxString::Format(_T("socket error occured: %d "), socket->LastError()));

			//sometimes this error will happen after TestSocketReadable (socket flag changed)
			if(socket->LastError() == 7){
				
			}

			//notify UI
            //break;
        }

		char head[4] = {0};

		int packetLen = 0;
		char packet[MSN_P2P_DATA_PACKET_LENGTH] = {0};	
		
		bool fileEnd = session->GetFtManager()->ReadFileData(sessionID, packet, MSN_P2P_DATA_PACKET_LENGTH, packetLen);


		P2PMessage* dataMsg = 
			session->MakeP2PDataMessage(StringUtil::ToLong(sessionID), item->GetMessageID(), item->GetFileOffset()-packetLen, item->GetFileSize(), packetLen, 0x1000030, packet);
	
		wxMemoryBuffer buf;
		dataMsg->EncodeBinaryHeader(buf);
		buf.AppendData(packet, packetLen);

		delete dataMsg;

		StringUtil::WriteInt(head, 0, buf.GetDataLen());
		SendChunk(head, 4);
		SendChunk((char*)buf.GetData(), buf.GetDataLen());
		
		//DEBUG(_T("p2p file data: \r\n") + StringUtil::ToHexFormat((unsigned char*)buf.GetData(), buf.GetDataLen()));

		session->GetFtManager()->NotifyUpdatedEvent(sessionID, packetLen);

		
		if(fileEnd){
			
			//read the ACK message from receiver
			ReadChunk(head, 4);
			DEBUG(_T("p2p ack len:\r\n") + StringUtil::ToHexFormat((unsigned char*)head, 4));

			int len = StringUtil::ReadInt(head, 0, 4);
			char* chunk = new char[len];
			ReadChunk(chunk, len);

			DEBUG(_T("p2p ack data:\r\n") + StringUtil::ToHexFormat((unsigned char*)chunk, len));

			P2PMessage* msg = new P2PMessage;
			msg->DecodeBody(chunk, len);			

			if(msg->GetHeader()->flag == 0x02){
				DEBUG(_T("p2p connection received ACK message "));		
				session->GetFtManager()->NotifyFinishedEvent(sessionID);
			}

			delete[] chunk;
			delete msg;

			DEBUG(_T("p2p connection send BYE message "));

			session->GetContact(0);

			SendP2PByeMessage();

/*
			SLPMessage* slp = session->MakeSLPMessage(item->message->GetSlpMessage(), _T("bye"), _T("application/x-msnmsgr-sessionclosebody"));
			P2PMessage* bye = session->MakeP2PMessage(StringUtil::ToLong(sessionID), slp);

			wxMemoryBuffer byeBuf;
			bye->EncodeBinaryHeader(byeBuf);
			bye->GetSlpMessage()->Encode(byeBuf);

			StringUtil::WriteInt(head, 0, byeBuf.GetDataLen());
			SendChunk(head, 4);
			SendChunk((char*)byeBuf.GetData(), byeBuf.GetDataLen());

			delete bye;
*/
			break;
		}
    }

	Close();

	DEBUG(_T("connection thread exit:")+ wxString::Format(_T("%d"),this->GetId()));
    return 0;	
}

